﻿using System;
using System.Linq;
using System.Linq.Expressions;
using Castle.DynamicProxy;
using HardMock.Domain;
using HardMock.Recording;

namespace HardMock
{
    class ReplayerAspect : IInterceptor
    {
        private readonly IRepository repository;
        private readonly IIdentifierFactory identifierFactory;
        private readonly IMethodCallFactory methodCallFactory;

        internal ReplayerAspect(IRepository repository, IIdentifierFactory identifierFactory, IMethodCallFactory methodCallFactory)
        {
            this.repository = repository;
            this.identifierFactory = identifierFactory;
            this.methodCallFactory = methodCallFactory;
        }

        public void Intercept(IInvocation invocation)
        {
            var methodCall = methodCallFactory.Create(invocation);
            var identifier = identifierFactory.Create(methodCall);
            var callResult = FindCallResult(methodCall, identifier);
            MockInvocation(invocation, callResult);
        }

        private static void MockInvocation(IInvocation invocation, CallResult callResult)
        {
            var argumentsToMock = invocation.Arguments;
            var recordedArguments = callResult.Arguments.ToList();

            invocation.ReturnValue = callResult.ReturnValue;

            for (var i = 0; i < argumentsToMock.Count(); i++)
            {
                argumentsToMock[i] = recordedArguments[i].Value;
            }
        }

        private CallResult FindCallResult(MethodCall methodCall, string identifier)
        {
            bool isRecorded;

            var callResult = repository.TryGet(identifier, out isRecorded);

            if (!isRecorded)
            {
                throw new NotRecordedException(string.Format("call {{{0}}} is not recorded", methodCall));
            }
            return callResult;
        }

        public void Override<T, TR>(Expression<Func<T, TR>> func, TR value)
        {
            var methodCallToOverride = new MethodCallExpression<Func<T, TR>>(func);
            var identifier = identifierFactory.Create(methodCallToOverride);

            var returnValue = FindCallResult(methodCallToOverride, identifierFactory.Create(methodCallToOverride));
            repository.Update(identifier, new CallResult(value, returnValue.Arguments));
        }
    }
}